home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 September / PCWorld_2008-09_cd.bin / v cisle / sadanastroju / lightning-0.8-tb-win.xpi / js / calCachedCalendar.js < prev    next >
Text File  |  2008-02-18  |  21KB  |  485 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Sun Microsystems, Inc. code.
  15.  *
  16.  * The Initial Developers of the Original Code are
  17.  *   Philipp Kewisch <mozilla@kewis.ch>
  18.  *   Daniel Boelzle <daniel.boelzle@sun.com>
  19.  * Portions created by the Initial Developer are Copyright (C) 2007
  20.  * the Initial Developer. All Rights Reserved.
  21.  *
  22.  * Contributor(s):
  23.  *
  24.  * Alternatively, the contents of this file may be used under the terms of
  25.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  26.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.  * in which case the provisions of the GPL or the LGPL are applicable instead
  28.  * of those above. If you wish to allow use of your version of this file only
  29.  * under the terms of either the GPL or the LGPL, and not to allow others to
  30.  * use your version of this file under the terms of the MPL, indicate your
  31.  * decision by deleting the provisions above and replace them with the notice
  32.  * and other provisions required by the GPL or the LGPL. If you do not delete
  33.  * the provisions above, a recipient may use your version of this file under
  34.  * the terms of any one of the MPL, the GPL or the LGPL.
  35.  *
  36.  * ***** END LICENSE BLOCK ***** */
  37.  
  38. function calCachedCalendarObserverHelper(home, isCachedObserver) {
  39.     this.home = home;
  40.     this.isCachedObserver = isCachedObserver;
  41. }
  42. calCachedCalendarObserverHelper.prototype = {
  43.     isCachedObserver: false,
  44.  
  45.     onStartBatch: function() {
  46.         this.home.mObservers.notify("onStartBatch");
  47.     },
  48.  
  49.     onEndBatch: function() {
  50.         this.home.mObservers.notify("onEndBatch");
  51.     },
  52.  
  53.     onLoad: function(calendar) {
  54.         if (this.isCachedObserver) {
  55.             this.home.mObservers.notify("onLoad", [this.home]);
  56.         } else {
  57.             // start sync action after uncached calendar has been loaded.
  58.             // xxx todo, think about:
  59.             // although onAddItem et al have been called, we need to fire
  60.             // an additional onLoad completing the refresh call (->composite)
  61.             var home = this.home;
  62.             home.synchronize(
  63.                 function(status) {
  64.                     home.mObservers.notify("onLoad", [home]);
  65.                 });
  66.         }
  67.     },
  68.  
  69.     onAddItem: function(aItem) {
  70.         if (this.isCachedObserver) {
  71.             this.home.mObservers.notify("onAddItem", arguments);
  72.         }
  73.     },
  74.  
  75.     onModifyItem: function(aNewItem, aOldItem) {
  76.         if (this.isCachedObserver) {
  77.             this.home.mObservers.notify("onModifyItem", arguments);
  78.         }
  79.     },
  80.  
  81.     onDeleteItem: function(aItem) {
  82.         if (this.isCachedObserver) {
  83.             this.home.mObservers.notify("onDeleteItem", arguments);
  84.         }
  85.     },
  86.  
  87.     onError: function(aErrNo, aMessage) {
  88.         this.home.mObservers.notify("onError", arguments);
  89.     },
  90.  
  91.     onPropertyChanged: function(aCalendar, aName, aValue, aOldValue) {
  92.         if (!this.isCachedObserver) {
  93.             this.home.mObservers.notify("onPropertyChanged", [this.home, aName, aValue, aOldValue]);
  94.         }
  95.     },
  96.  
  97.     onPropertyDeleting: function(aCalendar, aName) {
  98.         if (!this.isCachedObserver) {
  99.             this.home.mObservers.notify("onPropertyDeleting", [this.home, aName]);
  100.         }
  101.     }
  102. };
  103.  
  104. function calCachedCalendar(uncachedCalendar) {
  105.     this.wrappedJSObject = this;
  106.     this.mSyncQueue = [];
  107.     this.mObservers = new calListenerBag(Components.interfaces.calIObserver);
  108.     uncachedCalendar.superCalendar = this;
  109.     uncachedCalendar.addObserver(new calCachedCalendarObserverHelper(this, false));
  110.     this.mUncachedCalendar = uncachedCalendar;
  111.     this.setupCachedCalendar();
  112.  
  113.     if (this.supportsChangeLog) {
  114.         var updateTimer = this.getProperty("cache.updateTimer");
  115.         if (updateTimer === null) {
  116.             updateTimer = 4; // override for changelog based providers
  117.         }
  118.         var timerCallback = {
  119.             mCalendar: this,
  120.             notify: function(timer) {
  121.                 LOG("[calCachedCalendar] replay timer");
  122.                 this.mCalendar.refresh();
  123.             }
  124.         };
  125.         this.mReplayTimer = Components.classes["@mozilla.org/timer;1"]
  126.                                       .createInstance(Components.interfaces.nsITimer);
  127.         this.mReplayTimer.initWithCallback(timerCallback,
  128.                                            updateTimer * 60 * 1000,
  129.                                            Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
  130.     }
  131.  
  132.     // Take care of the inital synchronization
  133.     this.refresh();
  134. }
  135. calCachedCalendar.prototype = {
  136.     QueryInterface: function cCC_QueryInterface(aIID) {
  137.         return doQueryInterface(this, calCachedCalendar.prototype, aIID,
  138.                                 [Components.interfaces.calICachedCalendar,
  139.                                  Components.interfaces.calICalendar,
  140.                                  Components.interfaces.nsISupports]);
  141.     },
  142.  
  143.     mCachedCalendar: null,
  144.     mCachedObserver: null,
  145.     mUncachedCalendar: null,
  146.     mObservers: null,
  147.     mSuperCalendar: null,
  148.     mReplayTimer: null,
  149.  
  150.     onCalendarUnregistering: function() {
  151.         if (this.mReplayTimer) {
  152.             this.mReplayTimer.cancel();
  153.             this.mReplayTimer = null;
  154.         }
  155.         if (this.mCachedCalendar) {
  156.             this.mCachedCalendar.removeObserver(this.mCachedObserver);
  157.             // Although this doesn't really follow the spec, we know the
  158.             // storage calendar's deleteCalendar method is synchronous.
  159.             // TODO put changes into a different calendar and delete
  160.             // afterwards.
  161.             this.mCachedCalendar.QueryInterface(Components.interfaces.calICalendarProvider)
  162.                                 .deleteCalendar(this.mCachedCalendar, null);
  163.             this.mCachedCalendar = null;
  164.         }
  165.     },
  166.  
  167.     setupCachedCalendar: function cCC_setupCachedCalendar() {
  168.         try {
  169.             if (this.mCachedCalendar) { // this is actually a resetupCachedCalendar:
  170.                 // Although this doesn't really follow the spec, we know the
  171.                 // storage calendar's deleteCalendar method is synchronous.
  172.                 // TODO put changes into a different calendar and delete
  173.                 // afterwards.
  174.                 this.mCachedCalendar.QueryInterface(Components.interfaces.calICalendarProvider)
  175.                                     .deleteCalendar(this.mCachedCalendar, null);
  176.                 if (this.supportsChangeLog) {
  177.                     // start with full sync:
  178.                     this.mUncachedCalendar.resetLog();
  179.                 }
  180.             } else {
  181.                 var calType = getPrefSafe("calendar.cache.type", "storage");
  182.                 // While technically, the above deleteCalendar should delete the
  183.                 // whole calendar, this is nothing more than deleting all events
  184.                 // todos and properties. Therefore the initialization can be
  185.                 // skipped.
  186.                 cachedCalendar = Components.classes["@mozilla.org/calendar/calendar;1?type=" + calType]
  187.                                            .createInstance(Components.interfaces.calICalendar);
  188.                 switch (calType) {
  189.                     case "memory":
  190.                         if (this.supportsChangeLog) {
  191.                             // start with full sync:
  192.                             this.mUncachedCalendar.resetLog();
  193.                         }
  194.                         break;
  195.                     case "storage":
  196.                         var file = getCalendarDirectory();
  197.                         file.append("cache.sqlite");
  198.                         var uri = getIOService().newFileURI(file);
  199.                         uri.spec += ("?id=" + this.id);
  200.                         cachedCalendar.uri = uri;
  201.                         cachedCalendar.id = this.id;
  202.                         break;
  203.                     default:
  204.                         throw new Error("unsupported cache calendar type: " + calType);
  205.                 }
  206.                 cachedCalendar.setProperty("relaxedMode", true);
  207.                 cachedCalendar.superCalendar = this;
  208.                 if (!this.mCachedObserver) {
  209.                     this.mCachedObserver = new calCachedCalendarObserverHelper(this, true);
  210.                 }
  211.                 cachedCalendar.addObserver(this.mCachedObserver);
  212.                 this.mCachedCalendar = cachedCalendar;
  213.             }
  214.         } catch (exc) {
  215.             Components.utils.reportError(exc);
  216.         }
  217.     },
  218.  
  219.     mPendingSync: null,
  220.     mSyncQueue: null,
  221.     synchronize: function cCC_synchronize(respFunc) {
  222.         this.mSyncQueue.push(respFunc);
  223.         if (this.mSyncQueue.length > 1) { // don't use mPendingSync here
  224.             LOG("[calCachedCalendar] sync in action/pending.");
  225.             return this.mPendingSync;
  226.         }
  227.  
  228.         var this_ = this;
  229.         function emptyQueue(status) {
  230.             var queue = this_.mSyncQueue;
  231.             this_.mSyncQueue = [];
  232.             function execResponseFunc(func) {
  233.                 try {
  234.                     func(status);
  235.                 } catch (exc) {
  236.                     ASSERT(false, exc);
  237.                 }
  238.             }
  239.             queue.forEach(execResponseFunc);
  240.             LOG("[calCachedCalendar] sync queue empty.");
  241.             var op = this_.mPendingSync;
  242.             this_.mPendingSync = null;
  243.             return op;
  244.         }
  245.  
  246.         if (this.offline) {
  247.             return emptyQueue(Components.results.NS_OK);
  248.         }
  249.  
  250.         if (this.supportsChangeLog) {
  251.             LOG("[calCachedCalendar] Doing changelog based sync for calendar " + this.uri.spec);
  252.             var opListener = {
  253.                 onResult: function(op, result) {
  254.                     if (!op || !op.isPending) {
  255.                         var status = (op ? op.status : Components.results.NS_OK);
  256.                         ASSERT(Components.isSuccessCode(status), "replay action failed: " + (op ? op.id : "<unknown>"));
  257.                         LOG("[calCachedCalendar] replayChangesOn finished.");
  258.                         emptyQueue(status);
  259.                     }
  260.                 }
  261.             };
  262.             this.mPendingSync = this.mUncachedCalendar.replayChangesOn(this.mCachedCalendar, opListener);
  263.             return this.mPendingSync;
  264.         }
  265.  
  266.         LOG("[calCachedCalendar] Doing full sync for calendar " + this.uri.spec);
  267.         // TODO put changes into a different calendar and delete
  268.         // afterwards.
  269.         var completeListener = {
  270.             hasRenewedCalendar: false,
  271.             onGetResult: function cCC_oOC_cL_onGetResult(aCalendar,
  272.                                                          aStatus,
  273.                                                          aItemType,
  274.                                                          aDetail,
  275.                                                          aCount,
  276.                                                          aItems) {
  277.                 if (Components.isSuccessCode(aStatus)) {
  278.                     if (!this.hasRenewedCalendar) {
  279.                         // TODO instead of deleting the calendar and creating a new
  280.                         // one, maybe we want to do a "real" sync between the
  281.                         // existing local calendar and the remote calendar.
  282.                         this_.setupCachedCalendar();
  283.                         this.hasRenewedCalendar = true;
  284.                     }
  285.                     for each (var item in aItems) {
  286.                         this_.mCachedCalendar.addItem(item, null);
  287.                     }
  288.                 }
  289.             },
  290.  
  291.             onOperationComplete: function cCC_oOC_cL_onOperationComplete(aCalendar,
  292.                                                                          aStatus,
  293.                                                                          aOpType,
  294.                                                                          aId,
  295.                                                                          aDetail) {
  296.                 ASSERT(Components.isSuccessCode(aStatus), "getItems failed: " + aStatus);
  297.                 emptyQueue(aStatus);
  298.             }
  299.         };
  300.         this.mPendingSync = this.mUncachedCalendar.getItems(Components.interfaces.calICalendar.ITEM_FILTER_ALL_ITEMS,
  301.                                                             0, null,  null, completeListener);
  302.         return this.mPendingSync;
  303.     },
  304.  
  305.     onOfflineStatusChanged: function cCC_onOfflineStatusChanged(aNewState) {
  306.         if (aNewState) {
  307.             // Going offline: (XXX get items before going offline?) => we may ask the user to stay online a bit longer
  308.         } else {
  309.             // Going online (start replaying changes to the remote calendar)
  310.             this.refresh();
  311.         }
  312.     },
  313.  
  314.     get superCalendar() {
  315.         return this.mSuperCalendar && this.mSuperCalendar.superCalendar || this;
  316.     },
  317.     set superCalendar(val) {
  318.         return (this.mSuperCalendar = val);
  319.     },
  320.  
  321.     get offline() {
  322.         return getIOService().offline;
  323.     },
  324.     get supportsChangeLog() {
  325.         return (this.mUncachedCalendar instanceof Components.interfaces.calIChangeLog);
  326.     },
  327.  
  328.     get canRefresh() { // enable triggering sync using the reload button
  329.         return true;
  330.     },
  331.     refresh: function() {
  332.         if (this.mUncachedCalendar.canRefresh && !this.offline) {
  333.             return this.mUncachedCalendar.refresh(); // will trigger synchronize once the calendar is loaded
  334.         } else {
  335.             var this_ = this;
  336.             return this.synchronize(
  337.                 function(status) { // fire completing onLoad for this refresh call
  338.                     this_.mCachedObserver.onLoad(this_.mCachedCalendar);
  339.                 });
  340.         }
  341.     },
  342.  
  343.     addObserver: function(aObserver) {
  344.         this.mObservers.add(aObserver);
  345.     },
  346.     removeObserver: function(aObserver) {
  347.         this.mObservers.remove(aObserver);
  348.     },
  349.  
  350.     addItem: function(item, listener) {
  351.         return this.adoptItem(item.clone(), listener);
  352.     },
  353.     adoptItem: function(item, listener) {
  354.         if (this.offline) {
  355.             ASSERT(false, "unexpected!");
  356.             if (listener) {
  357.                 listener.onOperationComplete(this, Components.interfaces.calIErrors.CAL_IS_READONLY,
  358.                                              Components.interfaces.calIOperation.ADD, null, null);
  359.             }
  360.             return null;
  361.         }
  362.         // Forwarding add/modify/delete to the cached calendar using the calIObserver
  363.         // callbacks would be advantageous, because the uncached provider could implement
  364.         // a true push mechanism firing without being triggered from within the program.
  365.         // But this would mean the uncached provider fires on the passed
  366.         // calIOperationListener, e.g. *before* it fires on calIObservers
  367.         // (because that order is undefined). Firing onOperationComplete before onAddItem et al
  368.         // would result in this facade firing onOperationComplete even though the modification
  369.         // hasn't yet been performed on the cached calendar (which happens in onAddItem et al).
  370.         // Result is that we currently stick to firing onOperationComplete if the cached calendar
  371.         // has performed the modification, see below:
  372.         var this_ = this;
  373.         var opListener = {
  374.             onGetResult: function(calendar, status, itemType, detail, count, items) {
  375.                 ASSERT(false, "unexpected!");
  376.             },
  377.             onOperationComplete: function(calendar, status, opType, id, detail) {
  378.                 if (Components.isSuccessCode(status)) {
  379.                     this_.mCachedCalendar.addItem(detail, listener);
  380.                 } else if (listener) {
  381.                     listener.onOperationComplete(this_, status, opType, id, detail);
  382.                 }
  383.             }
  384.         }
  385.         return this.mUncachedCalendar.adoptItem(item, opListener);
  386.     },
  387.  
  388.     modifyItem: function(newItem, oldItem, listener) {
  389.         if (this.offline) {
  390.             ASSERT(false, "unexpected!");
  391.             if (listener) {
  392.                 listener.onOperationComplete(this, Components.interfaces.calIErrors.CAL_IS_READONLY,
  393.                                              Components.interfaces.calIOperation.MODIFY, null, null);
  394.             }
  395.             return null;
  396.         }
  397.         // Forwarding add/modify/delete to the cached calendar using the calIObserver
  398.         // callbacks would be advantageous, because the uncached provider could implement
  399.         // a true push mechanism firing without being triggered from within the program.
  400.         // But this would mean the uncached provider fires on the passed
  401.         // calIOperationListener, e.g. *before* it fires on calIObservers
  402.         // (because that order is undefined). Firing onOperationComplete before onAddItem et al
  403.         // would result in this facade firing onOperationComplete even though the modification
  404.         // hasn't yet been performed on the cached calendar (which happens in onAddItem et al).
  405.         // Result is that we currently stick to firing onOperationComplete if the cached calendar
  406.         // has performed the modification, see below:
  407.         var this_ = this;
  408.         var opListener = {
  409.             onGetResult: function(calendar, status, itemType, detail, count, items) {
  410.                 ASSERT(false, "unexpected!");
  411.             },
  412.             onOperationComplete: function(calendar, status, opType, id, detail) {
  413.                 if (Components.isSuccessCode(status)) {
  414.                     this_.mCachedCalendar.modifyItem(detail, oldItem, listener);
  415.                 } else if (listener) {
  416.                     listener.onOperationComplete(this_, status, opType, id, detail);
  417.                 }
  418.             }
  419.         }
  420.         return this.mUncachedCalendar.modifyItem(newItem, oldItem, opListener);
  421.     },
  422.  
  423.     deleteItem: function(item, listener) {
  424.         if (this.offline) {
  425.             ASSERT(false, "unexpected!");
  426.             if (listener) {
  427.                 listener.onOperationComplete(this, Components.interfaces.calIErrors.CAL_IS_READONLY,
  428.                                              Components.interfaces.calIOperation.DELETE, null, null);
  429.             }
  430.             return null;
  431.         }
  432.         // Forwarding add/modify/delete to the cached calendar using the calIObserver
  433.         // callbacks would be advantageous, because the uncached provider could implement
  434.         // a true push mechanism firing without being triggered from within the program.
  435.         // But this would mean the uncached provider fires on the passed
  436.         // calIOperationListener, e.g. *before* it fires on calIObservers
  437.         // (because that order is undefined). Firing onOperationComplete before onAddItem et al
  438.         // would result in this facade firing onOperationComplete even though the modification
  439.         // hasn't yet been performed on the cached calendar (which happens in onAddItem et al).
  440.         // Result is that we currently stick to firing onOperationComplete if the cached calendar
  441.         // has performed the modification, see below:
  442.         var this_ = this;
  443.         var opListener = {
  444.             onGetResult: function(calendar, status, itemType, detail, count, items) {
  445.                 ASSERT(false, "unexpected!");
  446.             },
  447.             onOperationComplete: function(calendar, status, opType, id, detail) {
  448.                 if (Components.isSuccessCode(status)) {
  449.                     this_.mCachedCalendar.deleteItem(item, listener);
  450.                 } else if (listener) {
  451.                     listener.onOperationComplete(this_, status, opType, id, detail);
  452.                 }
  453.             }
  454.         }
  455.         return this.mUncachedCalendar.deleteItem(item, opListener);
  456.     }
  457. };
  458. (function() {
  459.     function defineForwards(proto, targetName, functions, getters, gettersAndSetters) {
  460.         function defineForwardGetter(attr) {
  461.             proto.__defineGetter__(attr, function() { return this[targetName][attr]; });
  462.         }
  463.         function defineForwardGetterAndSetter(attr) {
  464.             defineForwardGetter(attr);
  465.             proto.__defineSetter__(attr, function(value) { return (this[targetName][attr] = value); });
  466.         }
  467.         function defineForwardFunction(funcName) {
  468.             proto[funcName] = function() {
  469.                 var obj = this[targetName];
  470.                 return obj[funcName].apply(obj, arguments);
  471.             };
  472.         }
  473.         functions.forEach(defineForwardFunction);
  474.         getters.forEach(defineForwardGetter);
  475.         gettersAndSetters.forEach(defineForwardGetterAndSetter);
  476.     }
  477.  
  478.     defineForwards(calCachedCalendar.prototype, "mUncachedCalendar",
  479.                    ["getProperty", "setProperty", "deleteProperty"],
  480.                    ["sendItipInvitations", "type"], ["id", "name", "uri", "readOnly"]);
  481.     defineForwards(calCachedCalendar.prototype, "mCachedCalendar",
  482.                    ["getItem", "getItems", "startBatch", "endBatch"], [], []);
  483. })();
  484.  
  485.